﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using B303Langf.Parser.AST;
using B303Langf.Scanner;

namespace B303Langf.Parser
{
    public class ASTParser : IParser
    {
       private IScanner m_Scanner;
       private Token m_CurrentToken;

       public ASTParser( IScanner scanner )
       {
          m_Scanner = scanner;
          m_CurrentToken = scanner.Scan();
       }

       public Program ParseProgram()
       {
          var program = new Program();
          while( IsEither( TokenKind.Void, TokenKind.BoolType, TokenKind.IntegerType, TokenKind.CharType ) )
          {
             var dec = ParseDeclaration();
             Accept( TokenKind.Terminator );

             program.Declarations.Add( dec );
          }
          return program;
       }

       private void Accept( TokenKind expectedKind )
       {
          if( m_CurrentToken.Kind == expectedKind )
          {
             m_CurrentToken = m_Scanner.Scan();
          }
          else
          {
             throw new UnexpectedTokenException( "Expected " + expectedKind + " token, but found " + m_CurrentToken.Kind + " token" );
          }
       }

       private void AcceptIt()
       {
          m_CurrentToken = m_Scanner.Scan();
       }

       private Declaration ParseDeclaration()
       {
          var type = ParseTypeDenoter();
          var id = ParseIdentifier();
          switch( m_CurrentToken.Kind )
          {
             case TokenKind.AssignOperator:
                AcceptIt();
                var expr = ParseExpression();
                return new VariableDeclarationWithDefinition( type, id, expr );
             case TokenKind.ParanthesisBegin:
                AcceptIt();
                var decs = ParseDeclarations();
                Accept( TokenKind.ParanthesisEnd );
                var block = ParseBlock();
                return new FunctionDeclaration( type, id, decs, block );
             default:
                return new VariableDeclaration( type, id );
          }
       }

       private Declarations ParseDeclarations()
       {
          var declarations = new Declarations();
          switch( m_CurrentToken.Kind )
          {
             case TokenKind.Void:
             case TokenKind.BoolType:
             case TokenKind.IntegerType:
             case TokenKind.CharType:
                var dec = ParseDeclaration();
                declarations.List.Add( dec );
                while( IsEither( TokenKind.Comma ) )
                {
                   AcceptIt();
                   dec = ParseDeclaration();
                   declarations.List.Add( dec );
                }
                break;
             default:
                break; // nil is allowed
          }
          return declarations;
       }

       private Expression ParseExpression()
       {
          Expression result = ParseAssignmentExpression();
          switch( m_CurrentToken.Kind )
          {
             case TokenKind.AssignOperator:
                var op = ParseOperator();
                var expression = ParseExpression();
                result = new BinaryExpression( result, op, expression );
                break;
             default:
                break; // nil is allowed
          }
          return result;
       }

       private Expression ParseAssignmentExpression()
       {
          Expression result = ParseConditionalOrExpression();
          while( IsEither(
             TokenKind.ConditionalOrOperator ) )
          {
             var op = ParseOperator();
             var expression = ParseConditionalOrExpression();
             result = new BinaryExpression( result, op, expression );
          }
          return result;
       }

       private Expression ParseConditionalOrExpression()
       {
          Expression result = ParseConditionalAndExpression();
          while( IsEither(
             TokenKind.ConditionalAndOperator ) )
          {
             var op = ParseOperator();
             var expression = ParseConditionalAndExpression();
             result = new BinaryExpression( result, op, expression );
          }
          return result;
       }

       private Expression ParseConditionalAndExpression()
       {
          Expression result = ParseEqualityExpression();
          while( IsEither(
             TokenKind.EqualsOperator,
             TokenKind.NotEqualsOperator ) )
          {
             var op = ParseOperator();
             var expression = ParseEqualityExpression();
             result = new BinaryExpression( result, op, expression );
          }
          return result;
       }

       private Expression ParseEqualityExpression()
       {
          Expression result = ParseRelationalExpression();
          while( IsEither(
             TokenKind.LessThanOperator,
             TokenKind.GreaterThanOperator,
             TokenKind.LessThanOrEqualsOperator,
             TokenKind.GreaterThanOrEqualsOperator ) )
          {
             var op = ParseOperator();
             var expression = ParseRelationalExpression();
             result = new BinaryExpression( result, op, expression );
          }
          return result;
       }

       private Expression ParseRelationalExpression()
       {
          Expression result = ParseAdditiveExpression();
          while( IsEither(
             TokenKind.PlusOperator,
             TokenKind.MinusOperator ) )
          {
             var op = ParseOperator();
             var expression = ParseAdditiveExpression();
             result = new BinaryExpression( result, op, expression );
          }
          return result;
       }

       private Expression ParseAdditiveExpression()
       {
          Expression result = ParsePrimaryExpression();
          while( IsEither( 
             TokenKind.MultiplyOperator,
             TokenKind.DivisionOperator ) )
          {
             var op = ParseOperator(); // should we separate these?
             var expression = ParsePrimaryExpression();
             result = new BinaryExpression( result, op, expression );
          }
          return result;
       }

       private Expressions ParseExpressions()
       {
          var expressions = new Expressions();
          switch( m_CurrentToken.Kind )
          {
             case TokenKind.Identifier:
             case TokenKind.PlusOperator:
             case TokenKind.MinusOperator:
             case TokenKind.MultiplyOperator:
             case TokenKind.DivisionOperator:
             case TokenKind.AssignOperator:
             case TokenKind.EqualsOperator:
             case TokenKind.NotEqualsOperator:
             case TokenKind.LessThanOperator:
             case TokenKind.LessThanOrEqualsOperator:
             case TokenKind.GreaterThanOperator:
             case TokenKind.GreaterThanOrEqualsOperator:
             case TokenKind.ConditionalAndOperator:
             case TokenKind.ConditionalOrOperator:
             case TokenKind.IntegerLiteral:
             case TokenKind.CharArrayLiteral:
             case TokenKind.True:
             case TokenKind.False:
             case TokenKind.ParanthesisBegin:
                var expr = ParseExpression();
                expressions.List.Add( expr );
                while( IsEither( TokenKind.Comma ) )
                {
                   AcceptIt();
                   expr = ParseExpression();
                   expressions.List.Add( expr );
                }
                break;
             default:
                break; // nil is allowed
          }
          return expressions;
       }

       private Statement ParseStatement()
       {
          switch( m_CurrentToken.Kind )
          {
             case TokenKind.Void:
             case TokenKind.BoolType:
             case TokenKind.IntegerType:
             case TokenKind.CharType:
                var decl = ParseDeclaration();
                Accept( TokenKind.Terminator );
                return new DeclarationStatement( decl );
             case TokenKind.Identifier:
             case TokenKind.PlusOperator:
             case TokenKind.MinusOperator:
             case TokenKind.MultiplyOperator:
             case TokenKind.DivisionOperator:
             case TokenKind.AssignOperator:
             case TokenKind.EqualsOperator:
             case TokenKind.NotEqualsOperator:
             case TokenKind.LessThanOperator:
             case TokenKind.LessThanOrEqualsOperator:
             case TokenKind.GreaterThanOperator:
             case TokenKind.GreaterThanOrEqualsOperator:
             case TokenKind.ConditionalAndOperator:
             case TokenKind.ConditionalOrOperator:
             case TokenKind.IntegerLiteral:
             case TokenKind.CharArrayLiteral:
             case TokenKind.True:
             case TokenKind.False:
             case TokenKind.ParanthesisBegin:
                var expr = ParseExpression();
                Accept( TokenKind.Terminator );
                return new ExpressionStatement( expr );
             case TokenKind.If:
                AcceptIt();
                Accept( TokenKind.ParanthesisBegin );
                var conditionExpr = ParseExpression();
                Accept( TokenKind.ParanthesisEnd );
                var block = ParseBlock();
                switch( m_CurrentToken.Kind )
                {
                   case TokenKind.Else:
                      AcceptIt();
                      var elseBlock = ParseBlock();
                      return new IfStatement( conditionExpr, block, elseBlock );
                   default:
                      return new IfStatement( conditionExpr, block );
                }
             case TokenKind.While:
                AcceptIt();
                Accept( TokenKind.ParanthesisBegin );
                var condExpr = ParseExpression();
                Accept( TokenKind.ParanthesisEnd );
                var whileBlock = ParseBlock();
                return new WhileStatement( condExpr, whileBlock );
             case TokenKind.For:
                AcceptIt();
                Accept( TokenKind.ParanthesisBegin );
                Expression initExpr = ParseExpression();
                Accept( TokenKind.Terminator );
                var forCondExpr = ParseExpression();
                Accept( TokenKind.Terminator );
                var incrExpr = ParseExpression();
                Accept( TokenKind.ParanthesisEnd );
                var b = ParseBlock();
                return new ForStatement( initExpr, forCondExpr, incrExpr, b );
             case TokenKind.Return:
                AcceptIt();
                var returnExpr = ParseExpression();
                Accept( TokenKind.Terminator );
                return new ReturnStatement( returnExpr );
             case TokenKind.Output:
                AcceptIt();
                var outputExpr = ParseExpression();
                Accept( TokenKind.Terminator );
                return new OutputStatement( outputExpr );
             case TokenKind.Input:
                AcceptIt();
                var id = ParsePrimaryExpression();
                Accept( TokenKind.Terminator );
                return new InputStatement( id );
             default:
                throw new UnexpectedTokenException( "Expected statement but found " + m_CurrentToken.Kind + "(" + m_CurrentToken.Spelling + ")" );
          }
       }

       private Block ParseBlock()
       {
          var block = new Block();
          Accept( TokenKind.BlockBegin );
          while( IsEither(
             TokenKind.Void,
             TokenKind.BoolType,
             TokenKind.IntegerType,
             TokenKind.CharType,
             TokenKind.Identifier,
             TokenKind.PlusOperator,
             TokenKind.MinusOperator,
             TokenKind.MultiplyOperator,
             TokenKind.DivisionOperator,
             TokenKind.AssignOperator,
             TokenKind.EqualsOperator,
             TokenKind.NotEqualsOperator,
             TokenKind.LessThanOperator,
             TokenKind.LessThanOrEqualsOperator,
             TokenKind.GreaterThanOperator,
             TokenKind.GreaterThanOrEqualsOperator,
             TokenKind.ConditionalAndOperator,
             TokenKind.ConditionalOrOperator,
             TokenKind.True,
             TokenKind.False,
             TokenKind.IntegerLiteral,
             TokenKind.CharArrayLiteral,
             TokenKind.ParanthesisBegin,
             TokenKind.If, 
             TokenKind.While,
             TokenKind.For,
             TokenKind.Return,
             TokenKind.Input,
             TokenKind.Output ) )
          {
             var statement = ParseStatement();
             block.Statements.Add( statement );
          }
          Accept( TokenKind.BlockEnd );
          return block;
       }

       private PrimaryExpression ParsePrimaryExpression()
       {
          switch( m_CurrentToken.Kind )
          {
             case TokenKind.Identifier:
                var id = ParseIdentifier();
                switch (m_CurrentToken.Kind)
	             {
                   case TokenKind.ArrayIndicatorBegin:
                      var ai = ParseArrayIndexer();
                      return new ArrayVariablePrimaryExpression( id, ai );
                   case TokenKind.ParanthesisBegin:
                      AcceptIt();
                      var expr = ParseExpressions();
                      Accept( TokenKind.ParanthesisEnd );
                      return new FunctionCallPrimaryExpression( id, expr );
                   default:
                      return new VariablePrimaryExpression( id );
	             }
             case TokenKind.PlusOperator:
             case TokenKind.MinusOperator:
                var op = ParseOperator();
                var exprPr = ParseExpression();
                return new OperatorModifiedExpressionPrimaryExpression( op, exprPr );
             case TokenKind.IntegerLiteral:
             case TokenKind.CharArrayLiteral:
             case TokenKind.CharLiteral:
             case TokenKind.True:
             case TokenKind.False:
                var literal = ParseTypeLiteral();
                return new TypeLiteralPrimaryExpression( literal );
             case TokenKind.ParanthesisBegin:
                AcceptIt();
                var expre = ParseExpression();
                Accept( TokenKind.ParanthesisEnd );
                return new ParanthesisPrimaryExpression( expre );
             default:
               throw new UnexpectedTokenException( "Expected primary but found " + m_CurrentToken.Kind + "(" + m_CurrentToken.Spelling + ")" );
          }
       }

       private Identifier ParseIdentifier()
       {
          var id = new Identifier( m_CurrentToken.Spelling );
          Accept( TokenKind.Identifier );
          return id;
       }

       private TypeLiteral ParseTypeLiteral()
       {
          switch( m_CurrentToken.Kind )
          {
             case TokenKind.IntegerLiteral:
                var itl = ParseIntegerLiteral();
                return itl;
             case TokenKind.CharLiteral:
                var stl = ParseCharLiteral();
                return stl;
             case TokenKind.CharArrayLiteral:
                var satl = ParseCharArrayLiteral();
                return satl;
             case TokenKind.True:
             case TokenKind.False:
                var btl = ParseBoolLiteral();
                return btl;
             default:
                throw new UnexpectedTokenException( "Expected literal but found " + m_CurrentToken.Kind + "(" + m_CurrentToken.Spelling + ")" );
          }
       }

       private CharArrayTypeLiteral ParseCharArrayLiteral()
       {
          var stl = new CharArrayTypeLiteral( m_CurrentToken.Spelling );
          AcceptIt();
          return stl;
       }

       private IntegerTypeLiteral ParseIntegerLiteral()
       {
          var itl = new IntegerTypeLiteral( m_CurrentToken.Spelling );
          AcceptIt();
          return itl;
       }

       private CharTypeLiteral ParseCharLiteral()
       {
          var stl = new CharTypeLiteral( m_CurrentToken.Spelling );
          AcceptIt();
          return stl;
       }

       private BoolTypeLiteral ParseBoolLiteral()
       {
          var btl = new BoolTypeLiteral( m_CurrentToken.Spelling );
          AcceptIt();
          return btl;
       }

       private TypeDenoter ParseTypeDenoter()
       {
          switch( m_CurrentToken.Kind )
          {
             case TokenKind.Void:
                AcceptIt();
                return new VoidTypeDenoter();
             case TokenKind.CharType:
                AcceptIt();
                switch( m_CurrentToken.Kind )
                {
                   case TokenKind.ArrayIndicatorBegin:
                      var ai = ParseArrayIndexer();
                      return new CharTypeDenoter( ai );
                   default:
                      return new CharTypeDenoter();
                }
             case TokenKind.IntegerType:
                AcceptIt();
                switch( m_CurrentToken.Kind )
                {
                   case TokenKind.ArrayIndicatorBegin:
                      var ai = ParseArrayIndexer();
                      return new IntegerTypeDenoter( ai );
                   default:
                      return new IntegerTypeDenoter();
                }
             case TokenKind.BoolType:
                AcceptIt();
                switch ( m_CurrentToken.Kind)
	             {
                   case TokenKind.ArrayIndicatorBegin:
                      var ai = ParseArrayIndexer();
                      return new BoolTypeDenoter( ai );
                   default:
                      return new BoolTypeDenoter();
	             }
             default:
                throw new UnexpectedTokenException( "Expected type but found " + m_CurrentToken.Kind + "(" + m_CurrentToken.Spelling + ")" );
          }
       }

       private Operator ParseOperator()
       {
          switch( m_CurrentToken.Kind )
          {
             case TokenKind.PlusOperator:
                AcceptIt();
                return new PlusOperator();
             case TokenKind.MinusOperator:
                AcceptIt();
                return new MinusOperator();
             case TokenKind.MultiplyOperator:
                
                AcceptIt();
                return new MultiplyOperator();
             case TokenKind.DivisionOperator:
                
                AcceptIt();
                return new DivisionOperator();
             case TokenKind.AssignOperator:
                
                AcceptIt();
                return new AssignOperator();
             case TokenKind.EqualsOperator:
                
                AcceptIt();
                return new EqualsOperator();
             case TokenKind.NotEqualsOperator:
             
                AcceptIt();
                return new NotEqualsOperator();
             case TokenKind.LessThanOperator:
                
                AcceptIt();
                return new LessThanOperator();
             case TokenKind.LessThanOrEqualsOperator:
             
                AcceptIt();
                return new LessThanOrEqualsOperator();
             case TokenKind.GreaterThanOperator:

                AcceptIt();
                return new GreaterThanOperator();
             case TokenKind.GreaterThanOrEqualsOperator:
                
                AcceptIt();
                return new GreaterThanOrEqualsOperator();
             case TokenKind.ConditionalAndOperator:
             
                AcceptIt();
                return new ConditionalAndOperator();
             case TokenKind.ConditionalOrOperator:
                AcceptIt();
                return new ConditionalOrOperator();
             default:
                throw new UnexpectedTokenException( "Expected operator but found " + m_CurrentToken.Kind + "(" + m_CurrentToken.Spelling + ")" );
          }
       }

       private ArrayIndexer ParseArrayIndexer()
       {
          Accept( TokenKind.ArrayIndicatorBegin );
          switch( m_CurrentToken.Kind )
          {
             case TokenKind.Identifier:
             case TokenKind.PlusOperator:
             case TokenKind.MinusOperator:
             case TokenKind.MultiplyOperator:
             case TokenKind.DivisionOperator:
             case TokenKind.AssignOperator:
             case TokenKind.EqualsOperator:
             case TokenKind.NotEqualsOperator:
             case TokenKind.LessThanOperator:
             case TokenKind.LessThanOrEqualsOperator:
             case TokenKind.GreaterThanOperator:
             case TokenKind.GreaterThanOrEqualsOperator:
             case TokenKind.ConditionalAndOperator:
             case TokenKind.ConditionalOrOperator:
             case TokenKind.IntegerLiteral:
             case TokenKind.CharArrayLiteral:
             case TokenKind.True:
             case TokenKind.False:
             case TokenKind.ParanthesisBegin:
                var expr = ParseExpression();
                Accept( TokenKind.ArrayIndicatorEnd );
                return new ArrayIndexer( expr );
             default:
                Accept( TokenKind.ArrayIndicatorEnd );
                return new ArrayIndexer();
          }
       }

       private bool IsEither( params TokenKind[] kinds )
       {
          return kinds.Any( x => x == m_CurrentToken.Kind );
       }
    }
}
